package org.fastsyncer.parser.ldap;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;

import org.apache.commons.lang.StringUtils;
import org.fastsyncer.common.constant.ConnectorConstant;
import org.fastsyncer.common.entity.LdapConfig;
import org.fastsyncer.common.entity.Mapping;
import org.fastsyncer.common.entity.MappingTask;
import org.fastsyncer.connector.common.LdapConstant;
import org.fastsyncer.connector.ldap.LdapConnector;
import org.fastsyncer.parser.framework.Parser;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.LdapTemplate;

public final class LdapParser implements Parser {

    private static volatile LdapParser instance = null;

    private LdapParser() {
    }

    public static LdapParser getInstance() {
        if (null == instance) {
            synchronized (LdapParser.class) {
                if (null == instance) {
                    instance = new LdapParser();
                }
            }
        }
        return instance;
    }

    private static Logger logger = LoggerFactory.getLogger(LdapParser.class);

    /**
     * 获取dnName
     * @param config
     * @param row
     * @return
     * @throws NullPointerException
     */
    private String getDnName(LdapConfig config, Map<String, Object> row) throws NullPointerException {
        // 获取ldap的dn路径
        String dn = config.getDn();
        // 节点全路径dn
        String dnName = String.valueOf(row.get(dn));
        // 排除dn,防止在新增修改操作时重复设置该属性
        row.remove(dn);
        if (StringUtils.isBlank(dnName)) {
            throw new NullPointerException("The 'dnName' can no be null.");
        }
        // 检查dn格式是否满足标准格式,ou=myorg666
        String prefixDn = config.getDn() + "=";
        if (!dnName.startsWith(prefixDn)) {
            dnName = prefixDn + dnName;
        }
        return dnName;
    }

    private String k;

    private Object v;

    /**
     * 转换基本属性名称和值类型
     * @param ctx
     * @param row
     */
    private void convertAttrAdd(DirContextAdapter ctx, Map<String, Object> row) {
        for (Map.Entry<String, Object> obj : row.entrySet()) {
            k = obj.getKey();
            v = obj.getValue();
            if (null == v) {
                continue;
            }
            // 如果是数组
            if (v instanceof List) {
                @SuppressWarnings("unchecked")
                List<String> l = (List<String>) v;
                ctx.setAttributeValues(k, l.toArray(), true);
                continue;
            }
            // 目标源名称
            ctx.addAttributeValue(k, String.valueOf(v));
        }
        row = null;
    }

    /**
     * 转换基本属性名称和值类型
     * @param ctx
     * @param row
     */
    private void convertAttrUpdate(DirContextAdapter ctx, Map<String, Object> row) {
        for (Map.Entry<String, Object> obj : row.entrySet()) {
            k = obj.getKey();
            v = obj.getValue();
            if (null == v) {
                continue;
            }
            // 如果是数组
            if (v instanceof List) {
                @SuppressWarnings("unchecked")
                List<String> l = (List<String>) v;
                ctx.setAttributeValues(k, l.toArray(), true);
                continue;
            }
            // 目标源名称
            ctx.setAttributeValue(k, String.valueOf(v));
        }
        row = null;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public List<Map<String, Object>> batchQuery(MappingTask mappingTask, Integer startPos, Integer endPos) throws Exception {
        Mapping sourceMapping = mappingTask.getSourceMapping();
        LdapConnector conn = LdapConnector.getInstance();
        // 数据源实例
        LdapConfig sourceConfig = (LdapConfig) sourceMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(sourceConfig);

        // 转换器
        AttributesMapper mapper = new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs) throws NamingException, javax.naming.NamingException {
                Map<String, Object> r = new HashMap<String, Object>();
                NamingEnumeration<String> iDs = attrs.getIDs();
                while (iDs.hasMoreElements()) {
                    String key = (String) iDs.nextElement();
                    Object value = attrs.get(key).get();
                    r.put(key, value);
                }
                return r;
            }
        };

        /**
         * 期望返回的数据格式: [ {ou=scdl, objectClass=top, description=四川电力,
         * homeDirectory=ou=scdl_part1,ou=scdl,dc=aostar,dc=com, cn=sss}, ... ]
         */
        String filter = sourceMapping.getExecuteCommond().get(ConnectorConstant.OPERTION_QUERY);
        /**
         * 0:OBJECT_SCOPE,搜索指定的命名对象。 <br />
         * 1:ONELEVEL_SCOPE,只搜索指定命名对象的一个级别，这是缺省值。<br />
         * 2:SUBTREE_SCOPE,搜索以指定命名对象为根结点的整棵树,就像递归查询
         **/
        SearchControls controls = new SearchControls();
        // 查询方式
        Integer searchScope = LdapConstant.OPERS.get(sourceConfig.getSearchScope());
        controls.setSearchScope(searchScope);
        List<Map<String, Object>> search = ldapTemplate.search("", filter, controls, mapper);
        return search;
    }

    @Override
    public Integer[] batchInsert(MappingTask mappingTask, List<Map<String, Object>> rows, Integer[] pos, Queue<JSONObject> reasonQuene) throws Exception {
        // 获取数据源、目标源同步的属性
        Mapping targetMapping = mappingTask.getTargetMapping();
        List<Map<String, Object>> targetFileds = targetMapping.getFileds();
        // 检查同步属性
        if (targetFileds.isEmpty()) {
            throw new Exception("同步的属性不存在，请检查!");
        }

        // 获取目标源实例
        LdapConfig targetConfig = (LdapConfig) targetMapping.getConfig();
        String tDn = targetConfig.getDn();
        // 目标源继承对象
        Object[] textendObjects = targetConfig.getExtendObjects().toArray();

        // 2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        LdapConnector conn = LdapConnector.getInstance();
        LdapTemplate targetTemplate = conn.getLdapTemplate(targetConfig);
        DirContextAdapter ctx = null;
        String dnName = null;
        String prefixDn = tDn + "=";
        // 总数和成功数
        int total = 0;
        int success = 0;
        for (Map<String, Object> row : rows) {
            try {
                // 设置数据DN
                dnName = String.valueOf(row.get(tDn));
                // 检查dn格式是否满足标准格式,ou=myorg666
                if (!dnName.startsWith(prefixDn)) {
                    dnName = prefixDn + dnName;
                }
                ctx = new DirContextAdapter(dnName);
                // 排除dn参数值
                row.remove(tDn);

                // 4.转换基本属性名称和值类型
                this.convertAttrAdd(ctx, row);

                ctx.setAttributeValues("objectclass", textendObjects);
                targetTemplate.bind(ctx);
                success++;
            } catch (Exception e) {
                // 记录错误信息
                JSONObject err = new JSONObject();
                if (pos != null && pos.length == 2) {
                    err.put("startPos", pos[0]);
                    err.put("endPos", pos[1]);
                }
                err.put("message", e.getMessage());
                reasonQuene.offer(err);
            } finally {
                conn.close(ctx);
                total++;
            }
        }
        rows = null;
        return new Integer[] { total, success };
    }

    @Override
    public boolean insert(MappingTask task, Map<String, Object> row) throws Exception {
        // 1.解析数据源
        Mapping targetMapping = task.getTargetMapping();
        LdapConnector conn = LdapConnector.getInstance();
        LdapConfig c = (LdapConfig) targetMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(c);

        // 2.获取ldap的dn路径
        String dnName = this.getDnName(c, row);

        // 3.设置域 cn=ae86,ou=scdl,dc=aostar,dc=com
        // 在2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        DirContextAdapter ctx = new DirContextAdapter(dnName);
        try {
            // 继承对象
            Object[] extendObjects = c.getExtendObjects().toArray();
            ctx.setAttributeValues("objectclass", extendObjects);

            // 4.转换基本属性名称和值类型
            this.convertAttrAdd(ctx, row);

            // 5.绑定
            ldapTemplate.bind(ctx);
            logger.info("insert success.");
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            conn.close(ctx);
        }
        return true;
    }

    @Override
    public boolean update(MappingTask task, Map<String, Object> row) throws Exception {
        // 1.解析目标源配置
        Mapping targetMapping = task.getTargetMapping();
        // 执行修改
        LdapConnector conn = LdapConnector.getInstance();
        LdapConfig c = (LdapConfig) targetMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(c);

        // 2.获取ldap的dn路径
        String dnName = this.getDnName(c, row);

        // 3.设置域 cn=ae86,ou=scdl,dc=aostar,dc=com
        // 在2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookupContext(dnName);
        try {
            // 继承对象
            Object[] extendObjects = c.getExtendObjects().toArray();
            ctx.setAttributeValues("objectclass", extendObjects);

            // 4.转换基本属性名称和值类型
            this.convertAttrUpdate(ctx, row);

            // 5.修改属性
            ldapTemplate.modifyAttributes(ctx);
            logger.info("update success.");
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            conn.close(ctx);
        }
        return true;
    }

    @Override
    public boolean delete(MappingTask task, Map<String, Object> row) throws Exception {
        // 1.解析目标源配置
        Mapping targetMapping = task.getTargetMapping();
        LdapConnector conn = LdapConnector.getInstance();
        LdapConfig c = (LdapConfig) targetMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(c);

        // 2.获取ldap的dn路径
        String dnName = this.getDnName(c, row);

        // 3.解除绑定
        ldapTemplate.unbind(dnName);
        row = null;
        logger.info("delete success.");
        return true;
    }

}
